/*
 * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

#include "TransistorNoiseSource.h"
#include "RNG.h"
#include "Crypto.h"
#include <Arduino.h>

/**
 * \class TransistorNoiseSource TransistorNoiseSource.h <TransistorNoiseSource.h>
 * \brief Processes the signal from a transistor-based noise source.
 *
 * This class processes input from a transistor-based noise source, such as
 * that described by <a href="http://robseward.com/misc/RNG2/">Rob Seward</a>.
 * See that Web page for full details on how such noise sources work,
 * how the output should be used, and caveats for the unwary.
 * For convenience, Rob's circuit is reproduced below:
 *
 * \image html transistor_noise_source.png
 *
 * The following example shows how to initialize a transistor-based noise
 * source and use it with \link RNGClass RNG\endlink.  The noise is read
 * from the A1 pin on the Arduino and stirred into the random number pool
 * on a regular basis.  For more information, see the documentation for
 * \link RNGClass RNG\endlink.
 *
 * \code
 * #include <Crypto.h>
 * #include <RNG.h>
 * #include <TransistorNoiseSource.h>
 *
 * // Noise source to seed the random number generator.
 * TransistorNoiseSource noise(A1);
 *
 * void setup() {
 *     // Initialize the random number generator with the application tag
 *     // "MyApp 1.0" and load the previous seed from EEPROM address 500.
 *     RNG.begin("MyApp 1.0", 500);
 *
 *     // Add the noise source to the list of sources known to RNG.
 *     RNG.addNoiseSource(noise);
 *
 *     // ...
 * }
 *
 * void loop() {
 *     // ...
 *
 *     // Perform regular housekeeping on the random number generator.
 *     RNG.loop();
 *
 *     // ...
 * }
 * \endcode
 *
 * \sa \link RNGClass RNG\endlink, NoiseSource, RingOscillatorNoiseSource
 */

/*

Theory of operation:

From Rob Seward's original design we need to find the median of the input
signal.  That is, the threshold at which half the signal is below the
threshold (a zero) and the other half is above the threshold (a one).
Rob used a histogram table to find the median which is memory-intensive.
We cannot afford to spend that much memory finding the median.

In this implementation we divide the signal up into "buckets" of 1024
samples.  We pick a starting threshold and count the number of ones
within the bucket.  If the number of ones is between 45% to 55% of the
total number of samples, then we use that threshold to convert the
bucket into output bits.  Otherwise we adjust the threshold up or down,
discard the bucket, and try again.

After a few buckets, the threshold naturally settles at the median without
needing a histogram.  The rest of the bucket processing can be done online
with storage needed only for the debiased output bits.

If the input voltage to the noise source is too low to generate noise,
then the delta between the minimum and maximum samples in the bucket will
be quite small.  This is used to detect disconnection of the noise source.
No output is generated when the noise source is disconnected.

With 1024 raw input samples we get roughly 256 output bits after
Von Neumann debiasing.  As a further check, the output will be discarded
if less than 192 bits are generated.  This can happen when the noise source
is connected or disconnected: only part of the bucket is valid.

One of the properties of Rob's circuit design is that over time the median
changes due to environmental factors and component wear.  Because we adjust
the threshold from bucket to bucket, it should naturally float up or down
to the new median level as the circuit's properties change.

*/

// Number of ADC values that can be generated by analogRead().
#define ADC_NUM             1024

// Number of samples to collect for a single noise "bucket".
#define SAMPLES_NUM         1024

// Calculate a percentage of the sample bucket size.
#define SAMPLES_PCT(num)    ((int)(((long)SAMPLES_NUM) * (num) / 100L))

// Expected spread between the minimum and maximum ADC readings for
// the noise source to be considered as operating correctly.
#define NOISE_SPREAD        (ADC_NUM / 8)

// Calibration states.
#define NOISE_NOT_CALIBRATING   0
#define NOISE_CALIBRATING       1

/**
 * \brief Constructs a new transitor-based noise source handler.
 *
 * \param pin The analog input pin that the noise will appear on.
 */
TransistorNoiseSource::TransistorNoiseSource(uint8_t pin)
    : threshold(ADC_NUM / 2)
    , _pin(pin)
    , calState(NOISE_CALIBRATING)
{
    // Configure the pin as an analog input with no pull-up.
    pinMode(pin, INPUT);
    digitalWrite(pin, LOW);

    // Start the bit collection routines.
    restart();
}

TransistorNoiseSource::~TransistorNoiseSource()
{
    restart();
}

bool TransistorNoiseSource::calibrating() const
{
    return calState != NOISE_NOT_CALIBRATING;
}

void TransistorNoiseSource::stir()
{
    // Keep track of the minimum and maximum while generating data
    // so that we can detect when the input voltage falls too low
    // for the circuit to generate noise.
    int value = analogRead(_pin);
    if (value < minValue)
        minValue = value;
    if (value > maxValue)
        maxValue = value;

    // Collect two bits of input and remove bias using the Von Neumann method.
    // If both bits are the same, then discard both.  Otherwise choose one
    // of the bits and output that one.  We have to do this carefully so that
    // instruction timing does not reveal the value of the bit that is chosen.
    uint8_t bit = ((threshold - value) >> 15) & 1; // Subtract and extract sign.
    if (count & 1) {
        if (prevBit ^ bit) {
            // The bits are different: add the new bit to the buffer.
            if (posn < sizeof(buffer)) {
                buffer[posn] = (buffer[posn] << 1) | bit;
                if (++bitNum >= 8) {
                    ++posn;
                    bitNum = 0;
                }
            }
        }
    } else {
        prevBit = bit;
    }

    // Keep a count of the number of raw 1 bits.
    ones += bit;

    // Bail out if we haven't collected enough samples for a full bucket yet.
    if (++count < SAMPLES_NUM)
        return;

    // If the maximum minus the minimum is too small, then there probably
    // is no signal or the input voltage is insufficient to generate noise.
    // Discard the entire bucket and return to calibration.
    if ((maxValue - minValue) < NOISE_SPREAD) {
        restart();
        calState = NOISE_CALIBRATING;
        threshold = ADC_NUM / 2; // Reacquire threshold when the signal returns.
        return;
    }

    // If the number of 1's is between 45% and 55% of the total count,
    // then we have a good bucket.  The threshold is at an appropriate level.
    if (ones >= SAMPLES_PCT(45) && ones <= SAMPLES_PCT(55)) {
        if (posn >= (sizeof(buffer) * 3 / 4)) {
            // The buffer is at least three-quarters full of debiased bits
            // so pass them onto output().  There may be less bits if we
            // lost or gained the signal half-way through the bucket.
            // Credit 4 bits of entropy for every 8 bits of output.
            output(buffer, posn, posn * 4);
        }
        restart();
        calState = NOISE_NOT_CALIBRATING;
        return;
    }

    // The threshold is not close enough to the mid-point of the signal.
    // Adjust the threshold, discard the bucket, and try again.
    if (ones < SAMPLES_PCT(25) || ones > SAMPLES_PCT(75)) {
        // We are a long way away from the mid-point, so move the threshold
        // by a large amount based on the delta to get closer quicker.
        threshold -= (SAMPLES_PCT(50) - ones) / 8;
    } else if (ones < SAMPLES_PCT(50)) {
        // Not enough ones so move the threshold down a bit.
        --threshold;
    } else {
        // Too many ones so move the threshold up a bit.
        ++threshold;
    }
    if (threshold < 0)
        threshold = 0;
    else if (threshold >= ADC_NUM)
        threshold = ADC_NUM - 1;
    restart();
    calState = NOISE_CALIBRATING;
}

/**
 * \brief Restarts the bit collection process for the next bucket.
 */
void TransistorNoiseSource::restart()
{
    clean(buffer);
    prevBit = 0;
    posn = 0;
    bitNum = 0;
    minValue = ADC_NUM - 1;
    maxValue = 0;
    count = 0;
    ones = 0;
}
